為什麼突然要動手做?有次讀書會Mentor說可以自己做一個new
,不過當下我對於prototype原型還不明白,以至於沒有聽懂...這次鐵人賽到這邊,也把prototype了解了,所以想說試試看怎麼自己做一個new
運算子~
先看看MDN說的,new它在做什麼。分成這4個步驟,把英文版簡略改成中文意思:
- 建立一個新物件。
- 將建構函式的prototype與新物件的原型[[prototype]]做連結。
- 呼叫建構函式,並將this綁定新物件。
- 如果建構函式沒有回傳任何值或回傳基本型別,則回傳新物件;如果建構函式有回傳值(非基本型別),會覆蓋原本的新物件成為最終結果。
按照MDN說的那幾個步驟試著做:
function myNew(fn,...args){}
宣告一個myNew函式,參數部分 fn
代表要傳入的建構函式,而 ...args
為 fn
的參數,args為陣列。
例如:原生寫法 new GetData("John")
,現在會像這樣呈現 mynew(GetData,"John")
。
為什麼是用...args
的方式帶入?因為我們不知道參數傳入的數量,而這樣不論傳入多少參數,都可以把參數打包成一個「陣列」,為ES6新增的功能。
範例:
function funcA(...args){
console.log(args);
}
funcA(1,2,3,4,5); // 印出[1,2,3,4,5]
function myNew(fn,...args){
const obj = {}; //步驟1 建立一個空物件
obj.__proto__ = fn.prototype; //步驟2 設定obj物件原型
}
上面這兩個步驟也可以改用Object.create
一行來呈現:
function myNew(fn,...args){
const obj = Object.create(fn.prototype); //步驟1+步驟2
}
const returnValue = obj.constructor(); //步驟3
這裡是用呼叫物件obj上的constructor屬性,通常constructor屬性是指向「建立該物件的建構函式」,也就是fn建構函式。
但要注意,如果constructor指向不一樣的函式,就會出現錯誤!
所以,有另一種寫法能確保傳入fn建構函式被呼叫,並明確綁定this:
const returnValue = fn.apply(obj, args); //步驟3
也就是呼叫fn建構函式,並用apply明確綁定this在obj。這樣建構函式內的this就會指向obj。
我們知道apply的參數是以一個「陣列」傳入,這樣可以靈活處理不確定數量的參數;而call需要依次列出所有的參數。
也連結到一開始說的,args是一個陣列,因此搭配apply。
剛剛第四步驟說到建構函式:
所以現在要判斷是否有回傳值,以及判斷回傳值的型別:
return returnValue instanceof Object ? returnValue : obj;
這段意思是returnValue如果是物件,就回傳returnValue作為結果;反之,則回傳新物件obj。
以上步驟就建構完成myNew
了。
原本用typeof
判斷型別,但陣列、null,都是回傳"object"
,無法具體區分型別。
而instanceof可檢查某物件是否為某建構函式的實例,透過該物件的原型鍊 [[Prototype]]
來判斷。
範例:
function Person(name){
this.name = name;
}
const student = new Person("小白");
console.log(student instanceof Person); //true
console.log(student instanceof Object); //true
console.log(Person instanceof Function); //true
console.log(null instanceof Object); //false
myNew完整程式碼:
function myNew(fn, ...args) {
// 1.建立一個新物件
// 2.將建構函式的prototype與新物件的原型[[prototype]]做連結
const obj = Object.create(fn.prototype);
// 3.呼叫建構函式,並將this綁定新物件
const returnValue = fn.apply(obj, args);
// 4.如果建構函式沒有回傳任何值或回傳基本型別,則回傳新物件;如果建構函式有回傳值(非基本型別),會覆蓋原本的新物件成為最終結果
return returnValue instanceof Object ? returnValue : obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
const child = myNew(Person, "小黑", 7);
console.log(child); //{name:"小黑",age:7}
用原生new:
function Person(name, age) {
this.name = name;
this.age = age;
}
const child = new Person("小黑", 7);
console.log(child);
比較myNew和原生new。
以上分享~謝謝!
MDN - new
MDN - instanceof
new operator — JavaScript | 為了瞭解原理,那就來實作一個 new 吧!